package com.android.task4; 

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Set;
import java.lang.Math;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;

public class Cache {
	
	//TODO: Prefetching strategy where and how
	//
	int cacheSize;
	HTTPClient httpC;
	
	public HashMap<String,BitmapObject> cache;
	
	public Cache(int cacheSize){
		this.cacheSize = cacheSize;
		cache = new HashMap<String,BitmapObject>();
		httpC = new HTTPClient();
	}
	
	public BitmapObject getBitmap(String tileUrl){
		
//		String tileURL = "http://nimbus.informatik.uni-stuttgart.de/~fapra-android/OSMTiles/"+zoom+"/"+x+"/"+y+".png";
		
		int x = 0;
		int y = 0;
		int zoom =0;
		String key = ""+x+","+y+","+zoom; 
		if(cache.containsKey(key)){
			Log.i("INFO","Cache hit");
			return cache.get(key);
		}
		else{
			Log.i("INFO","Cache miss, fetching");
			Bitmap tile = httpC.newRequest(x, y, zoom);
			BitmapObject bmo = new  BitmapObject(x,y,zoom,tile);
			addToCache(bmo);
			prefetchAnotherStep(bmo);
			return bmo;
		}
		
		
	}
	
	public BitmapObject getBitmap(int zoom, int x, int y){
		
//		String tileURL = "http://nimbus.informatik.uni-stuttgart.de/~fapra-android/OSMTiles/"+zoom+"/"+x+"/"+y+".png";
		String key = ""+x+","+y+","+zoom; 
	
		
		if(cache.containsKey(key)){
			Log.i("INFO","Cache hit");
			return cache.get(key);
		}
		else{
			Log.i("INFO","Cache miss, fetching");
			Bitmap tile = httpC.newRequest(x, y, zoom);
			BitmapObject bmo = new  BitmapObject(x,y,zoom,tile);
			addToCache(bmo);
			prefetchAnotherStep(bmo);
			return bmo;
		}
	}
	/**
	 * Function add bitmap to cache memory
	 * @param bitmapObject *.png file with map
	 */
	public void addToCache(BitmapObject bitmapObject){
		if(cache.size() == this.cacheSize){
			/*
			 *  TODO: !!! FUNCTION NOT TESTED !!!
			 * TILE CATCHING
			 * Idea of deleting unnecessary files:
			 * We're looking for these keys, which values are different from the last saved one
			 * in more than 2 digits, beginning from x. 
			 * F.ex: Our last saved position was "100,50,7" <x,y,zoom>. We're deleting all keys
			 * which are lower than "98,48,6" and greater than "102,52,8"
			 */
			
			//Identifier format: <x,y,zoom>;
			int xValue = bitmapObject.getX();
			int yValue = bitmapObject.getY();
			int zoomValue = bitmapObject.getZoom();
			String lowestBorderKey = (xValue - 2) + "," + (yValue - 2) +"," + (zoomValue - 1);
			String highestBorderKey = (xValue + 2) + "," + (yValue + 2) +"," + (zoomValue + 1);
			
			//Set of keys from cache
			Set<String> setOfKeys =cache.keySet();
			//Converting set to array of objects, to allow indexing keys. 
			Object[] objOfKeys = setOfKeys.toArray();
			//Comparing all of the keys to the lower and highest one. If key is out of range - we remove it. 
			for(int i = 0; i < this.cacheSize; i++){
					if( (objOfKeys[i].toString().compareTo(highestBorderKey) > 0) ||
						(objOfKeys[i].toString().compareTo(lowestBorderKey) < 0) )
							cache.remove(objOfKeys[i]);
					Log.i("Cache file", "unnecessary element deleted from cache memory");
			}
			cache.put(bitmapObject.getIdentifier(), bitmapObject);
		}
		else{
			Log.i("INFO","Added to cache");
			cache.put(bitmapObject.getIdentifier(), bitmapObject);
		}
	}
	/**
	 * TODO: FUNCTION NOT TESTED !!!
	 * 
	 * Function is called when new object was downloaded from the server
	 * To decrease waiting for showing new tile, we try to predict next step, to allow taking next map tile from cache
	 * We check 8 surrounding tiles of current one + files with the same x and y values, but (zoom + 1) and (zoom - 1):
	 * ?	?	?
	 * ?	x	?
	 * ?	?	?
	 * 
	 * if they are already in cache, nothing happen. If not, we download new tiles. 
	 * Normally, 3 new tiles are downloaded. 
	 * @param bitmapObject current tile of map
	 */
	void prefetchAnotherStep(BitmapObject bitmapObject){
		String predictedKey;
		int x = bitmapObject.getX();
		int y = bitmapObject.getY();
		int zoom = bitmapObject.getZoom();
		boolean overZoom = false;
		boolean overX= false;
		boolean overY = false;
		//checking, if we are out of range
		// ex. if zoom == 0, we can't get from wepbage tile with lower zoom
		
		if( (zoom > 17) || (zoom < 1)) 
			overZoom = true;
		if(zoom < 8){
			if( (x < 1) || (x > Math.pow((double)2,(double)zoom)) )
					overX = true;
			if( (y < 1) || (y > Math.pow((double)2,(double)zoom)) ) 
				overY = true;
		}
		if(zoom == 9){
			if(( x < 242) || (x > 304)) overX = true;
			if( (y < 91) ||  (y > 210)) overY = true;}
		if(zoom == 10){
			if(( x < 484) || (x > 180)) overX = true;
			if( (y < 182) ||  (y > 421)) overY = true;}
		if(zoom == 11){
			if(( x < 1064) || (x > 1102)) overX = true;
			if( (y < 677) ||  (y > 719)) overY = true;}
		if(zoom == 12){
			if(( x < 4256) || (x > 4413)) overX = true;
			if( (y < 2706) ||  (y > 2880)) overY = true;}
		if(zoom == 13){
			if(( x < 4256) || (x > 4413)) overX = true;
			if( (y < 2706) ||  (y > 2880)) overY = true;}
		if(zoom == 14){
			if(( x < 8511) || (x > 8828)) overX = true;
			if( (y < 5412) ||  (y > 5761)) overY = true;}
		if(zoom == 15){
			if(( x < 17170) || (x > 17247)) overX = true;
			if( (y < 11281) ||  (y > 11321)) overY = true;}
		if(zoom == 16){
			if(( x < 34394) || (x > 34496)) overX = true;
			if( (y < 22562) ||  (y > 22602)) overY = true;}
		if(zoom == 17){
			if(( x < 68788) || (x > 68993)) overX = true;
			if( (y < 45124) ||  (y > 45205)) overY = true;}
		
		if((!overX) && (!overY) ){
			predictedKey = "" + (x+1) + "," + (y) + "," + zoom; 
				if(!cache.containsKey(predictedKey)){
					downloadNewTile(x + 1, y, zoom);
					Log.i("Cache file", "prefetched tile downloaded");
				}
				predictedKey = "" + (x+1) + "," + (y+1) + "," + zoom; 
				if(!cache.containsKey(predictedKey)){
					downloadNewTile(x + 1, y + 1, zoom);
					Log.i("Cache file", "prefetched tile downloaded");
				}
				predictedKey = "" + (x) + "," + (y+1) + "," + zoom; 
				if(!cache.containsKey(predictedKey)){
					downloadNewTile(x , y + 1, zoom);
					Log.i("Cache file", "prefetched tile downloaded");
				}
				predictedKey = "" + (x-1) + "," + (y+1) + "," + zoom; 
				if(!cache.containsKey(predictedKey)){
					downloadNewTile(x - 1, y + 1, zoom);
					Log.i("Cache file", "prefetched tile downloaded");
				}
				predictedKey = "" + (x-1) + "," + (y) + "," + zoom; 
				if(!cache.containsKey(predictedKey)){
					downloadNewTile(x - 1, y, zoom);
					Log.i("Cache file", "prefetched tile downloaded");
				}
				predictedKey = "" + (x-1) + "," + (y-1) + "," + zoom; 
				if(!cache.containsKey(predictedKey)){
					downloadNewTile(x - 1, y - 1, zoom);
					Log.i("Cache file", "prefetched tile downloaded");
				}
				predictedKey = "" + (x) + "," + (y-1) + "," + zoom; 
				if(!cache.containsKey(predictedKey)){
					downloadNewTile(x, y - 1, zoom);
					Log.i("Cache file", "prefetched tile downloaded");
				}
				predictedKey = "" + (x+1) + "," + (y-1) + "," + zoom; 
				if(!cache.containsKey(predictedKey)){
					downloadNewTile(x + 1, y - 1, zoom);
					Log.i("Cache file", "prefetched tile downloaded");
				}
				//ZOOM
//				if(!overZoom){
//					predictedKey = "" + (x) + "," + (y) + "," + (zoom + 1); 
//					if(!cache.containsKey(predictedKey)){
//						downloadNewTile(x, y , zoom+1);
//						Log.i("Cache file", "prefetched tile downloaded");
//					}
//					predictedKey = "" + (x) + "," + (y) + "," + (zoom - 1); 
//					if(!cache.containsKey(predictedKey)){
//						downloadNewTile(x, y , zoom-1);
//						Log.i("Cache file", "prefetched tile downloaded");
//					}
//				}
			}
			
			
	}
	/**
	 * Function that download Tile via HTTP protocol
	 * @param x x position of tile
	 * @param y y position of tile
	 * @param zoom zoom level
	 */
	void downloadNewTile(int x, int y, int zoom){
		Bitmap tile = httpC.newRequest(x, y, zoom);
		BitmapObject bmo = new  BitmapObject(x,y,zoom,tile);
		addToCache(bmo);
	}
}
